﻿using AgorApp.Roma.Model;
using AgorApp.Roma.ServiceContracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using AgorApp.Roma.Oracle.Services.Mappers;
using AgorApp.Roma.Model.DataTransferObjects;
using Newtonsoft.Json;
using System.Data.Entity;
using AutoMapper.Mappers;
using System.Data;
using AgorApp.Roma.Model.DataTransferObjects.Utils;
using AgorApp.Roma.Oracle.Services.Utils;

namespace AgorApp.Roma.Oracle.Services
{
    public class GruppiConsiliariService : IGruppiConsiliariService
    {
        public IConsiglieriAssembleaService ConsiglieriAssembleaService { get; private set; }
        public IGruppiConsiliariImagesService GruppiConsiliariImagesService { get; private set; }
        public ITagService TagService { get; private set; }

        public GruppiConsiliariService(IConsiglieriAssembleaService consiglieriAssembleaService,
            IGruppiConsiliariImagesService gruppiConsiliariImagesService,
            ITagService tagService)
        {
            this.ConsiglieriAssembleaService = consiglieriAssembleaService;
            this.GruppiConsiliariImagesService = gruppiConsiliariImagesService;
            this.TagService = tagService;

            MapperConfigure();
        }

        #region AutoMapper

        private ConfigurationStore config;
        private MappingEngine mapper;

        private void MapperConfigure()
        {
            config = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
            config.AssertConfigurationIsValid();
            mapper = new MappingEngine(config);
            config.AddProfile<DataTransferObjectsProfile>();
            config.AddProfile<GruppiConsiliariServiceProfile>();
            config.AssertConfigurationIsValid();
        }

        private class GruppiConsiliariServiceProfile : Profile
        {
            public override string ProfileName
            {
                get
                {
                    return this.GetType().Name;
                }
            }

            protected override void Configure()
            {
                base.Configure();

                SourceMemberNamingConvention = new PascalCaseNamingConvention();
                DestinationMemberNamingConvention = new UpperUnderscoreNamingConvention();

                CreateMap<GruppiConsiliari, GRUPPI_CONSILIARI>()
                    .ForMember(dest => dest.IN_MAGGIORANZA, opt => opt.ResolveUsing((GruppiConsiliari src) =>
                    {
                        return Convert.ToInt16(src.InMaggioranza);
                    }))
                    .ForMember(dest => dest.NOTE, opt => opt.ResolveUsing((GruppiConsiliari src) =>
                    {
                        JsonSerializerSettings settings = new JsonSerializerSettings();
                        settings.MissingMemberHandling = MissingMemberHandling.Ignore;
                        settings.NullValueHandling = NullValueHandling.Ignore;
                        return JsonConvert.SerializeObject(src.Note, settings);
                    }))
                    .ForSourceMember(src => src.ObjectState, opt => opt.Ignore())
                    .ForMember(dest => dest.CONSIGLIERI_ASSEMBLEA, opt => opt.Ignore());
                    
                CreateMap<GruppiConsiliariImages, GRUPPI_CONSILIARI_IMAGES>()
                    .ForSourceMember(src => src.ObjectState, opt => opt.Ignore())
                    .ForMember(dest => dest.GRUPPI_CONSILIARI, opt => opt.Ignore());

                CreateMap<Tag, TAG>()
                    .ForMember(dest => dest.TAG1, opt => opt.MapFrom(src => src.TagName))
                    .ForMember(dest => dest.ID_TIPO_TAG, opt => opt.MapFrom(src => (short)src.TagType))
                    .ForSourceMember(src => src.ObjectState, opt => opt.Ignore())
                    .ForMember(dest => dest.TAG_TYPE, opt => opt.Ignore())
                    .ForMember(dest => dest.ASSEMBLEE_CAPITOLINE, opt => opt.Ignore())
                    .ForMember(dest => dest.CONSIGLIERI_ASSEMBLEA, opt => opt.Ignore())
                    .ForMember(dest => dest.DELIBERE_ASSEMBLEA_CAPITOLINA, opt => opt.Ignore())
                    .ForMember(dest => dest.GRUPPI_CONSILIARI, opt => opt.Ignore())
                    .ForMember(dest => dest.NEWS, opt => opt.Ignore());
            }
        }
        #endregion

        #region CRUD operations

        #region Direct methods

        public ICollection<GruppiConsiliari> GetAll()
        {
            IEnumerable<GRUPPI_CONSILIARI> gruppi;
            using (var dbContext = new AgorAppRomaContext())
            {
                gruppi = dbContext.GRUPPI_CONSILIARI
                    .Include(src => src.CONSIGLIERI_ASSEMBLEA)
                    .Include(src => src.GRUPPI_CONSILIARI_IMAGES)
                    .Include(src => src.TAGS);
            }
            if (gruppi.Count() != 0)
                return mapper.Map<ICollection<GruppiConsiliari>>(gruppi);
            else return null;
        }

        public GruppiConsiliari Get(short key)
        {
            GRUPPI_CONSILIARI gruppo;
            using (var dbContext = new AgorAppRomaContext())
            {
                gruppo = dbContext.GRUPPI_CONSILIARI.Find(key);
                if (gruppo == null) return null;
                dbContext.Entry(gruppo).Collection(src => src.CONSIGLIERI_ASSEMBLEA).Load();
                dbContext.Entry(gruppo).Collection(src => src.GRUPPI_CONSILIARI_IMAGES).Load();
                dbContext.Entry(gruppo).Collection(src => src.TAGS).Load();
            }
            return mapper.Map<GruppiConsiliari>(gruppo);
        }

        public void Insert(GruppiConsiliari entity)
        {
            GRUPPI_CONSILIARI gruppo;
            using (var dbContext = new AgorAppRomaContext())
            {
                gruppo = dbContext.GRUPPI_CONSILIARI.Find(entity.IdGruppo);
            }
            if (gruppo != null)
                throw new InvalidOperationException("Gruppo esistente in banca dati");
            var deletedImages = (from img in gruppo.GRUPPI_CONSILIARI_IMAGES
                                 let actualImagesId = entity.GruppiConsiliariImages.Select(i => i.IdImage)
                                 where !actualImagesId.Contains(img.ID_IMAGE)
                                 select img).ToList();
            var deletedTags = (from tag in gruppo.TAGS
                               let actualTags = entity.Tags
                               .GroupBy(t => (int)t.TagType, t => t.TagName)
                               .ToDictionary(g => g.Key, g => g.AsEnumerable())
                               where !actualTags[tag.ID_TIPO_TAG].Contains(tag.TAG1)
                               select tag).ToList();

            gruppo = mapper.Map<GruppiConsiliari, GRUPPI_CONSILIARI>(entity, gruppo);

            using (var dbContext = new AgorAppRomaContext())
            {
                dbContext.GRUPPI_CONSILIARI.Add(gruppo);
                foreach(var img in gruppo.GRUPPI_CONSILIARI_IMAGES)
                {
                    dbContext.Entry(img).State = entity.GruppiConsiliariImages.Single(i => i.IdImage == img.ID_IMAGE).ObjectState.ToEntityState();
                }
            }
            
            //update entity
            entity = mapper.Map<GRUPPI_CONSILIARI, GruppiConsiliari>(gruppo, entity);
        }

        public void Update(GruppiConsiliari entity)
        {
            GRUPPI_CONSILIARI gruppo;
            using (var dbContext = new AgorAppRomaContext())
            {
                gruppo = dbContext.GRUPPI_CONSILIARI.Find(entity.IdGruppo);
            }
            if (gruppo == null)
                throw new InvalidOperationException("Gruppo inesistente in banca dati");
            var oldImgs = gruppo.GRUPPI_CONSILIARI_IMAGES.ToList();
            var oldTags = gruppo.TAGS.ToList();
            //mapper
            gruppo = mapper.Map<GruppiConsiliari, GRUPPI_CONSILIARI>(entity, gruppo);
            using (var context = new AgorAppRomaContext())
            {
                context.GRUPPI_CONSILIARI.Attach(gruppo);
                //state objects
                context.Entry(gruppo).State = (EntityState)(int)entity.ObjectState;

                foreach (var img in gruppo.GRUPPI_CONSILIARI_IMAGES)
                {
                    context.Entry(img).State = (EntityState)(int)(entity.GruppiConsiliariImages.Single(i => i.IdImage == img.ID_IMAGE).ObjectState);
                }
                foreach (var img in oldImgs)
                {
                    var i = gruppo.GRUPPI_CONSILIARI_IMAGES.SingleOrDefault(x => x.ID_IMAGE == img.ID_IMAGE);
                    if (i == null)
                    {
                        gruppo.GRUPPI_CONSILIARI_IMAGES.Add(img);
                        context.Entry(img).State = EntityState.Deleted;
                    }
                }

                foreach (var tag in gruppo.TAGS)
                {
                    context.Entry(tag).State = (EntityState)(int)(entity.Tags.Single(t => t.IdTag == tag.ID_TAG).ObjectState);
                }
                foreach (var tag in oldTags)
                {
                    var t = gruppo.TAGS.SingleOrDefault(x => x.ID_TAG == tag.ID_TAG);
                    if (t == null)
                    {
                        gruppo.TAGS.Add(tag);
                        context.Entry(tag).State = EntityState.Deleted;
                    }
                }

                //context
                context.SaveChanges();
            }
            //update entity
            entity = mapper.Map<GRUPPI_CONSILIARI, GruppiConsiliari>(gruppo, entity);
        }

        public void Delete(GruppiConsiliari entity)
        {
            GRUPPI_CONSILIARI gr;
            using (var dbContext = new AgorAppRomaContext())
            {
                gr = dbContext.GRUPPI_CONSILIARI.Find(entity.IdGruppo);
                if (gr == null)
                    throw new InvalidOperationException("Gruppo inesistente in banca dati");
                dbContext.GRUPPI_CONSILIARI.Remove(gr);
                dbContext.SaveChanges();
            }
        }

        #endregion

        #region Async methods

        public async Task<ICollection<GruppiConsiliari>> GetAllAsync()
        {
            return await Task.Run<ICollection<GruppiConsiliari>>(() =>
                GetAll());
        }

        public async Task<GruppiConsiliari> GetAsync(short key)
        {
            return await Task.Run<GruppiConsiliari>(() => Get(key));
        }

        public async Task InsertAsync(GruppiConsiliari entity)
        {
            await Task.Run(() => Insert(entity));
        }

        public async Task UpdateAsync(GruppiConsiliari entity)
        {
            await Task.Run(() => Update(entity));
        }

        public async Task DeleteAsync(GruppiConsiliari entity)
        {
            await Task.Run(() => Delete(entity));
        }

        #endregion

        #endregion

        #region Disposable

        private bool disposed = false;
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    mapper.Dispose();
                }
            }
            disposed = true;
        }

        #endregion
    }
}
